#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using IPSim.Data;
using IPSim.Util;

namespace IPSim.Core
{
    public class ARPInterface
    {
        private Dictionary<uint, MacAddress> m_arpCache;
        private EthernetInterface m_ethernetInterface1;
        private EthernetInterface m_ethernetInterface2;
        private EthernetInterface m_ethernetInterface3;
        private EthernetInterface m_ethernetInterface4;
        /// <summary>
        /// Resolves destIP IP address
        /// </summary>
        /// <param name="srcIP">local IP Address</param>
        /// <param name="destIP">IP Address to resolve</param>
        /// <param name="macAddress">Output parametar</param>
        /// <returns></returns>
        public Boolean LocalResolveRequest(IPAddress srcIP, IPAddress ipAddrToResolve, out MacAddress macAddress)
        {
            if (m_arpCache.ContainsKey(ipAddrToResolve.Address))
            {
                macAddress = m_arpCache[ipAddrToResolve.Address];
                Logger.log("[ARP:" + srcIP + "] Resolved locally: " + ipAddrToResolve + " -> " + macAddress, LogLevel.INFO);
                return true;
            }
            else 
            {
                Logger.log("[ARP:" + srcIP + "] Not resolved locally: " + ipAddrToResolve, LogLevel.INFO);
                InitiateResolve(srcIP, ipAddrToResolve);                
                macAddress = MacAddress.BroadcastAddress;
                return false;
            }
        }

        private void InitiateResolve(IPAddress srcIP, IPAddress ipAddrToResolve)
        {
            Logger.log("[ARP:" + srcIP + "]\t Poslat je zahtev za resolving adrese: " + ipAddrToResolve, LogLevel.INFO);
            if (m_ethernetInterface1 != null)
                m_ethernetInterface1.ReceivePacketFromUpperLevel(new ARPPacket(ARPPacket.ARPOp.REQUEST, m_ethernetInterface1.MacAddress, MacAddress.BroadcastAddress, srcIP, ipAddrToResolve), MacAddress.BroadcastAddress);
            if (m_ethernetInterface2 != null)
                m_ethernetInterface2.ReceivePacketFromUpperLevel(new ARPPacket(ARPPacket.ARPOp.REQUEST, m_ethernetInterface2.MacAddress, MacAddress.BroadcastAddress, srcIP, ipAddrToResolve), MacAddress.BroadcastAddress);
            if (m_ethernetInterface3 != null)
                m_ethernetInterface3.ReceivePacketFromUpperLevel(new ARPPacket(ARPPacket.ARPOp.REQUEST, m_ethernetInterface3.MacAddress, MacAddress.BroadcastAddress, srcIP, ipAddrToResolve), MacAddress.BroadcastAddress);
            if (m_ethernetInterface4 != null)
                m_ethernetInterface4.ReceivePacketFromUpperLevel(new ARPPacket(ARPPacket.ARPOp.REQUEST, m_ethernetInterface4.MacAddress, MacAddress.BroadcastAddress, srcIP, ipAddrToResolve), MacAddress.BroadcastAddress);
        }

        public ARPInterface(EthernetInterface ethernetInterface1, EthernetInterface ethernetInterface2, EthernetInterface ethernetInterface3, EthernetInterface ethernetInterface4)
        {
            m_ethernetInterface1 = ethernetInterface1;
            m_ethernetInterface2 = ethernetInterface2;
            m_ethernetInterface3 = ethernetInterface3;
            m_ethernetInterface4 = ethernetInterface4;
            if (m_ethernetInterface1 != null)
                m_ethernetInterface1.TransportEthernetData += new TransportEthernetDataEventHandler(this.ReceiveEthernetData);
            if (m_ethernetInterface2 != null)
                m_ethernetInterface2.TransportEthernetData += new TransportEthernetDataEventHandler(this.ReceiveEthernetData);
            if (m_ethernetInterface3 != null)
                m_ethernetInterface3.TransportEthernetData += new TransportEthernetDataEventHandler(this.ReceiveEthernetData);
            if (m_ethernetInterface4 != null)
                m_ethernetInterface4.TransportEthernetData += new TransportEthernetDataEventHandler(this.ReceiveEthernetData);
            m_arpCache = new Dictionary<uint, MacAddress>();
            m_arpCache.Add(IPAddress.BroadcastAddress.Address, MacAddress.BroadcastAddress);
            Logger.log("ARP Interface CREATED!", LogLevel.INFO);
        }

        public ARPInterface(EthernetInterface ethernetInterface)
            : this(ethernetInterface, null, null, null)
        { }

        public void UpdateCache(MacAddress macAddr, IPAddress ipAddr)
        {
            m_arpCache[ipAddr.Address] = macAddr;
        }

        public void TakeARPPacket(IEthernetData arpPacket)
        {

        }

        private void ReceiveEthernetData(object source, TransportEthernetDataEventArgs e)
        {
            ARPPacket tempPacket;
            if (e.EtherType == EthernetFrame.EtherTypes.ARP)
            {
                Logger.log("ARP Packet received.", LogLevel.INFO);
                tempPacket = e.EthernetData as ARPPacket;
                m_arpCache[tempPacket.SenderIPAddress.Address] = tempPacket.SenderMacAddress;
                Logger.log("ARP Cache updated: " + tempPacket.SenderIPAddress + " -> " + tempPacket.SenderMacAddress, LogLevel.INFO);
                if ((tempPacket.ArpOp == ARPPacket.ARPOp.REQUEST)&&(m_arpCache.ContainsKey(tempPacket.TargetIPAddress.Address)))
                {
                    Logger.log("Sending ARP REPLY packet for IP: " + tempPacket.TargetIPAddress, LogLevel.INFO);
                    if (m_ethernetInterface1 != null)
                        m_ethernetInterface1.ReceivePacketFromUpperLevel(new ARPPacket(ARPPacket.ARPOp.REPLY, m_arpCache[tempPacket.TargetIPAddress.Address], tempPacket.SenderMacAddress, tempPacket.TargetIPAddress, tempPacket.SenderIPAddress), MacAddress.BroadcastAddress);
                    if (m_ethernetInterface2 != null)
                        m_ethernetInterface2.ReceivePacketFromUpperLevel(new ARPPacket(ARPPacket.ARPOp.REPLY, m_arpCache[tempPacket.TargetIPAddress.Address], tempPacket.SenderMacAddress, tempPacket.TargetIPAddress, tempPacket.SenderIPAddress), MacAddress.BroadcastAddress);
                    if (m_ethernetInterface3 != null)
                        m_ethernetInterface3.ReceivePacketFromUpperLevel(new ARPPacket(ARPPacket.ARPOp.REPLY, m_arpCache[tempPacket.TargetIPAddress.Address], tempPacket.SenderMacAddress, tempPacket.TargetIPAddress, tempPacket.SenderIPAddress), MacAddress.BroadcastAddress);
                    if (m_ethernetInterface4 != null)
                        m_ethernetInterface4.ReceivePacketFromUpperLevel(new ARPPacket(ARPPacket.ARPOp.REPLY, m_arpCache[tempPacket.TargetIPAddress.Address], tempPacket.SenderMacAddress, tempPacket.TargetIPAddress, tempPacket.SenderIPAddress), MacAddress.BroadcastAddress);
                }
            }
        }
        
        private readonly static Type declaringType = typeof(ARPInterface);
    }
}
